feat(observability): full Prometheus metrics endpoint, Grafana dashboard, and indexer-lag alert (Closes #717) - #903
Conversation
…er#679) Add sender-initiated stream delegation: - Sender can designate a stream as delegatable - Delegatable streams can be reassigned to a new recipient - Already-claimed amounts stay with original recipient - Non-delegatable, canceled, and paused streams reject delegation - StreamDelegated event emitted with delegated_amount - New recipient can immediately claim remaining vested amount New functions: set_delegatable, delegate_stream New field: Stream.delegatable (default: false) New event: StreamDelegated 11 new delegation tests covering lifecycle, access controls, and edge cases. Also fixes pre-existing compilation errors: - Deduplicated imports (Address, Env imported twice) - Separated legacy EscrowVestingContract into escrow submodule to resolve __claim symbol clash between two #[contractimpl] blocks - Added missing `pub mod errors` declaration 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
…itik4ever#717) Adds the six metric families the issue requires to the existing /metrics endpoint: request_count and request_duration_ms (recorded in the request logger middleware), DB-backed stream_count / claim_count / cancel_count gauges refreshed at scrape time with a 60s TTL, and indexer_lag_seconds computed at scrape time so a stalled indexer produces a growing lag. Also ships a self-contained monitoring stack (Prometheus scrape config with basic auth, an indexer-lag > 60s alert, Grafana provisioning and a dashboard) and documents METRICS_AUTH. Unblocks the backend test suite by fixing a merge artifact in streamStore.ts (truncated transaction build block) that was a parse error, and updates stale route tests in index.test.ts / cors.test.ts that previously could not load. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
|
@maybay-dev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
@maybay-dev is attempting to deploy a commit to the ritik4ever's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Closes #717
What this fixes
The backend already mounted a
/metricsendpoint, but it only exposed five indexer counters. Issue #717 asked for a production-ready Prometheus setup covering:request_countandrequest_duration_ms(HTTP traffic)stream_countbroken down by status (scheduled,active,paused,completed,canceled)claim_countandcancel_countindexer_lag_seconds/metricsin productionAll six metric families were missing, and there was no Grafana/Prometheus configuration anywhere in the repo.
Root cause
Observability was wired only for the indexer loop (
eventsIndexedTotal,ledgersScannedTotal,lastIndexedLedger, etc.). Nothing recorded HTTP request traffic, stream/claim/cancel aggregates were computed only by the internalstreamMetricsservice (not exposed), and there was no definition of "indexer lag" — so no alert could exist. The/metricsroute existed (with basic auth viaMETRICS_AUTH) but served only the indexer counters.Separately, the backend test suite and type checker could not run at all on
main: a merge artifact instreamStore.tsleft a truncated transaction-builder block that was a parse error, and the app-level route tests inindex.test.ts/cors.test.tshad drifted from the routes they assert on. CI was red for structural reasons before any feature work.The fix and why
Metrics (
backend/src/services/metrics.ts)request_count(Counter, labeled by method/route/status) andrequest_duration_ms(Histogram) are recorded inrequestLogger, the single middleware every request passes through. Route labels use the matched route pattern (/api/streams/:id) to keep cardinality bounded.stream_count,claim_count,cancel_countare DB-backed gauges refreshed at scrape time with a 60s TTL cache — the same patternstreamMetrics.tsalready uses. Deriving them from the DB (computed status +stream_eventsrows) means claims/cancels are counted exactly once regardless of origin (on-chain indexer event or backendcancelStream), avoiding code-hook drift and double counting.indexer_lag_secondsis computed via prom-client'scollect()from the wall-clock time of the last successful indexer poll (recorded inindexer.tson each successful cycle). Because it's computed at scrape time, a completely stalled indexer still produces a growing lag value — which is exactly what the alert needs. It reads 0 until the first successful poll.Route (
backend/src/index.ts) — refreshes the DB-backed gauges inside/metrics(wrapped in try/catch so a scrape still succeeds if the DB is down), keeping the existingMETRICS_AUTHbasic-auth protection.Monitoring stack (
monitoring/) — self-contained Prometheus + Grafana setup: scrape config that sendsMETRICS_AUTHcredentials, an alert ruleindexer_lag_seconds > 60(withfor: 2m), Grafana provisioning with a dashboard covering all six metric families, and adocker-compose.yml+ README.METRICS_AUTHis documented inbackend/.env.example.CI unblockers (required so the PR's checks can run at all)
streamStore.ts: removed the truncated duplicate transaction-build block from a bad merge (parse error that broke the entire backend build and every test importing the module).index.test.ts: refreshed the mockedstreamStore/auth/dbexports and the route-stack invoke helpers (they were picking middleware likereadLimiterinstead of the real handler), and updated two stale/api/eventsassertions to match the current route (3-argcountAllEvents, default page size 20).cors.test.tsand the indexer/markComplete test mocks: added the newmetricsexports so the modules load.This approach was chosen over alternatives like scattering
.inc()calls through every route handler (drift-prone) or a side-car metrics exporter (infrastructure not present in this repo). The change is additive: no auth, payment, or on-chain contract code is touched, and the indexer change is two pure additions.How it was tested
services/metrics.test.ts): 100% statement coverage ofmetrics.ts— metric registration, request counting, DB refresh with TTL cache (including the cache-hit path), lag computation viacollect(), and the reset helpers.metrics.route.test.ts):/metricsreturns 200 with all six families present, rejects missing/invalidMETRICS_AUTH, and serves valid Prometheus text format.metrics,metrics.route,requestLogger,cors,index, and the indexer suites (7 pre-existing skips).mainwithgit stash— every failing suite on this branch also fails onmain(mostly suites that couldn't even load there). This PR fixes 4 suites that were load-broken onmain(index.test.ts,cors.test.ts,streamStore.reconcile,webhooks.integration) and adds zero new failures.tsc --noEmitwent from 12 parse errors (base) to 25 pre-existing latent errors in unrelated files (migrations,db.tsallowlist exports, config) — none in files touched by this PR. Lint: clean on all changed files.metrics.tsis 100% lines / 88% branches.Follow-ups worth filing separately
db.tsis missing the allowlist functionsgetAllowedAssets/addAllowedAsset/removeAllowedAsset/searchStreamsFts/syncFtsIndexthatindex.tsimports — the multi-token allowlist merge ([FEATURE] Add multi-token support (USDC, XLM, and custom SAC tokens) to contract #593) landed incomplete. This breaksassets.test.tsand severaltscerrors. The functions need to be implemented and the tests restored.validateEnv.test.tsenv-var drift,auth.test.tstimestamp/replay drift,contentType.test.tsstale 415 expectations,indexer.gap.test.tsbatch-count drift,stats.test.tsmodule resolution,streamStore.*test drift). They fail identically onmainand should be repaired in dedicated PRs.tscerrors inbackend/src/migrations/*(better-sqlite3Databasenamespace) andindexer.ts(rpcServerpossibly null,clawbackevent type) predate this change.